﻿bob.ns.setNs('bob.collections.extensions', {

    equals: function (other, fnEquals) {
        var array = this;

        if (bob.utils.isUndefinedOrNull(fnEquals)) {
            fnEquals = function (one, two) {
                return bob.utils.equals(one, two);
            };
        }
        else if (!bob.utils.isFunction(fnEquals)) {
            throw new Error('Wrong argument: fnEquals.');
        }

        var arrayEnum = bob.collections.getEnumerator(array);
        var otherEnum = bob.collections.getEnumerator(other);

        var bArrayNext = arrayEnum.next();
        var bOtherNext = otherEnum.next();
        while (bArrayNext && bOtherNext) {
            if (!fnEquals.call(arrayEnum.get_current(), arrayEnum.get_current(), otherEnum.get_current(), arrayEnum.get_index(), otherEnum.get_index())) {
                return false;
            }

            bArrayNext = arrayEnum.next();
            bOtherNext = otherEnum.next();
        }

        return (bArrayNext == bOtherNext) ? true : false;
    },

    //clone - implicitly from _last.

    any: function (fnAny) {
        var array = this;

        if (bob.utils.isUndefinedOrNull(fnAny)) {
            fnAny = function (index, item) { return true; };
        }
        else if (!bob.utils.isFunction(fnAny)) {
            throw new Error('Wrong argument: fnAny.');
        }

        var arrayEnum = bob.collections.getEnumerator(array);
        while (arrayEnum.next()) {
            if (fnAny.call(arrayEnum.get_current(), arrayEnum.get_index(), arrayEnum.get_current())) {
                return true;
            }
        }
        return false;
    },

    all: function (fnAll) {
        var array = this;

        if (!bob.utils.isFunction(fnAll)) {
            throw new Error('Wrong argument: fnAll.');
        }

        var fnAny = function (index, item) {
            return !fnAll.call(item, index, item);
        };
        return !bob.collections.extensions.any.call(array, fnAny);
    },

    forEach: function (fnForEach) {
        var array = this;

        if (!bob.utils.isFunction(fnForEach)) {
            throw new Error('Wrong argument: fnForEach.');
        }

        var arrayEnum = bob.collections.getEnumerator(array);
        while (arrayEnum.next()) {
            fnForEach.call(arrayEnum.get_current(), arrayEnum.get_index(), arrayEnum.get_current());
        }

        return array;
    },

    //v.2
    firstOrDefault: function (fnCondition, defaultValue) {
        var array = this;

        if (bob.utils.isUndefinedOrNull(fnCondition)) {
            fnCondition = function () { return true; };
        }
        else if (!bob.utils.isFunction(fnCondition)) {
            throw new Error('Wrong argument: fnCondition.');
        }

        var ret_val = defaultValue;

        bob.collections.extensions.any.call(array, function (index, item) {
            var bResult = fnCondition.call(item, index, item);
            if (bResult) {
                ret_val = item;
            }

            return bResult;
        });

        return ret_val;
    },

    //v.2
    lastOrDefault: function (fnCondition, defaultValue) {
        var array = this;

        if (bob.utils.isUndefinedOrNull(fnCondition)) {
            var count = bob.collections.extensions.count.call(array);
            var index = 0;
            fnCondition = function () { return (++index) == count; };
        }
        else if (!bob.utils.isFunction(fnCondition)) {
            throw new Error('Wrong argument: fnCondition.');
        }

        var ret_val = defaultValue;

        bob.collections.extensions.forEach.call(array, function (index, item) {
            var bResult = fnCondition.call(item, index, item);
            if (bResult) {
                ret_val = item;
            }
        });

        return ret_val;
    },

    //v.2
    select: function (fnMap) {
        var array = this;

        if (bob.utils.isUndefinedOrNull(fnMap)) {
            fnMap = function (index, item) { return true; };
        }
        else if (!bob.utils.isFunction(fnMap)) {
            throw new Error('Wrong argument: fnMap.');
        }

        var resCreator = bob.collections.getCollectionCreator(array);
        bob.collections.extensions.forEach.call(array, function (index, item) {
            resCreator.addItem(index, fnMap.call(item, index, item));
        });

        return resCreator.getValue();
    },

    //v.2
    where: function (fnCondition) {
        var array = this;

        if (!bob.utils.isFunction(fnCondition)) {
            throw new Error('Wrong argument: fnCondition.');
        }

        var resCreator = bob.collections.getCollectionCreator(array);
        bob.collections.extensions.forEach.call(array, function (index, item) {
            if (fnCondition.call(item, index, item)) {
                resCreator.addItem(index, item);
            }
        });

        return resCreator.getValue();
    },

    //v.2
    //except - implicitly from _last.

    //v.2
    atIndex: function (index) {
        var array = this;

        var bFound = false;
        var ret_val = bob.collections.extensions.firstOrDefault.call(array, function (itemIndex, item) {
            bFound = bob.utils.equals(index, itemIndex);
            return bFound;
        });
        if (bFound) {
            return ret_val;
        }

        throw new Error(bob.string.formatString('Cannot find item at index {0}.', index));
    },

    //v.2
    combine: function (arrayOther) {
        var array = this;

        var arrayClone = bob.collections.extensions.clone.call(array);
        bob.collections.extensions.clone.call(arrayOther, null, arrayClone);
        return arrayClone;
    },

    //v.2
    toObject: function (fnProperty, fnValue) {
        var array = this;

        if (!bob.utils.isFunction(fnProperty)) {
            throw new Error('Wrong agrument: fnProperty.');
        }
        if (bob.utils.isUndefinedOrNull(fnValue)) {
            fnValue = function (index, item) {
                return item;
            };
        }
        else if (!bob.utils.isFunction(fnValue)) {
            throw new Error('Wrong argument: fnValue.');
        }

        var result = {};
        bob.collections.extensions.forEach.call(array, function (index, item) {
            var prop = fnProperty.call(item, index, item);
            if (bob.utils.isUndefinedOrNull(prop)) {
                throw new Error(bob.string.formatString('Wrong property name: {0}.', prop));
            }

            result[prop] = fnValue.call(item, index, item);
        });

        return result;
    },

    //v.2
    toArray: function (fnItem) {
        var obj = this;

        if (bob.utils.isUndefinedOrNull(fnItem)) {
            fnItem = function (index, item) {
                return item;
            };
        }
        else if (!bob.utils.isFunction(fnItem)) {
            throw new Error('Wrong argument: fnItem.');
        }

        var result = [];
        bob.collections.extensions.forEach.call(obj, function (prop, value) {
            result.push(fnItem.call(value, prop, value));
        });

        return result;
    },

    //v.2
    groupBy: function (fnKey, fnItem) {
        var arr = this;

        if (bob.utils.isUndefinedOrNull(fnKey)) {
            fnKey = function (index, item) {
                return index;
            };
        }
        else if (!bob.utils.isFunction(fnKey)) {
            throw new Error('Wrong argument: fnKey.');
        }

        if (bob.utils.isUndefinedOrNull(fnItem)) {
            fnItem = function (index, item) {
                return item;
            };
        }
        else if (!bob.utils.isFunction(fnItem)) {
            throw new Error('Wrong argument: fnItem.');
        }

        var result = {};
        bob.collections.extensions.forEach.call(arr, function (index, item) {
            var key = fnKey.call(item, index, item);
            if (bob.utils.isUndefinedOrNull(result[key])) {
                result[key] = [];
            }
            result[key].push(fnItem.call(item, index, item));
        });
        return result;
    },

    //v.2
    aggregate: function (fnForEach, fnResult) {
        var arr = this;

        if (!bob.utils.isFunction(fnForEach)) {
            throw new Error('Wrong argument: fnForEach.');
        }
        if (!bob.utils.isFunction(fnResult)) {
            throw new Error('Wrong argument: fnResult.');
        }

        bob.collections.extensions.forEach.call(arr, fnForEach);
        return fnResult();
    },

    //v.2
    count: function (fnCondition) {
        var array = this;

        var ret_val = 0;
        if (bob.utils.isUndefinedOrNull(fnCondition)) {
            fnCondition = function (index, item) {
                return true;
            };
        }
        else if (!bob.utils.isFunction(fnCondition)) {
            throw new Error('Wrong argument: fnCondition');
        }

        return bob.collections.extensions.aggregate.call(array, function (index, item) {
            if (fnCondition.call(item, index, item)) {
                ret_val++;
            }
        }, function () {
            return ret_val;
        });
    },

    //v.2
    sum: function (fnItem) {
        var arr = this;

        if (bob.utils.isUndefinedOrNull(fnItem)) {
            fnItem = function (index, item) { return item; };
        }
        else if (!bob.utils.isFunction(fnItem)) {
            throw new Error('Wrong argument: fnItem.');
        }

        var result = 0;
        return bob.collections.extensions.aggregate.call(arr, function (index, item) {
            result += fnItem.call(item, index, item);
        }, function () {
            return result;
        });
    },

    //v.2
    avg: function (fnItem) {
        var arr = this;

        if (bob.utils.isUndefinedOrNull(fnItem)) {
            fnItem = function (index, item) { return item; };
        }
        else if (!bob.utils.isFunction(fnItem)) {
            throw new Error('Wrong argument: fnItem.');
        }

        var divider = 0;
        var sum = bob.collections.extensions.sum.call(arr, function (index, item) {
            divider++;
            return fnItem.call(item, index, item);
        });

        if (divider == 0) {
            return 0;
        }
        return sum / divider;
    },

    //v.2
    min: function (fnItem) {
        var arr = this;

        if (bob.utils.isUndefinedOrNull(fnItem)) {
            fnItem = function (index, item) { return item; };
        }
        else if (!bob.utils.isFunction(fnItem)) {
            throw new Error('Wrong argument: fnItem.');
        }

        var bEmpty = true;
        var result = undefined;
        return bob.collections.extensions.aggregate.call(arr, function (index, item) {
            if (bEmpty) {
                result = fnItem.call(item, index, item);
                bEmpty = false;
            }
            else {
                result = Math.min(result, fnItem.call(item, index, item));
            }
        }, function () {
            if (!bEmpty) {
                return result;
            }

            throw new Error('Cannot calculate min: collection is empty.');
        });
    },

    //v.2
    max: function (fnItem) {
        var arr = this;

        if (bob.utils.isUndefinedOrNull(fnItem)) {
            fnItem = function (index, item) { return item; };
        }
        else if (!bob.utils.isFunction(fnItem)) {
            throw new Error('Wrong argument: fnItem.');
        }

        var bEmpty = true;
        var result = undefined;
        return bob.collections.extensions.aggregate.call(arr, function (index, item) {
            if (bEmpty) {
                result = fnItem.call(item, index, item);
                bEmpty = false;
            }
            else {
                result = Math.max(result, fnItem.call(item, index, item));
            }
        }, function () {
            if (!bEmpty) {
                return result;
            }

            throw new Error('Cannot calculate max: collection is empty.');
        });
    },

    //v3
    contains: function (objItem) {
        var arr = this;

        var fnAny = function (index, item) {
            return bob.utils.equals(item, objItem);
        };

        return bob.collections.extensions.any.call(arr, fnAny);
    },

    //v3
    //indexOf: implicitly from _last

    recursiveWhere: function (fnCondition, fnGetChildren) {
        var arr = this;

        if (!bob.utils.isFunction(fnCondition)) {
            throw new Error('Wrong argument: fnCondition.');
        }
        if (!bob.utils.isFunction(fnGetChildren)) {
            throw new Error('Wrong argument: fnGetChildren.');
        }

        var result = [];
        bob.collections.extensions.forEach.call(arr, function (index, item) {
            if (fnCondition.call(item, index, item)) {
                result.push(item);
            }
            else {
                var children = fnGetChildren.call(item, index, item);
                var moreResults = bob.collections.extensions.recursiveWhere.call(children, fnCondition, fnGetChildren);
                for (var i = 0; i < moreResults.length; i++) {
                    result.push(moreResults[i]);
                }
            }
        });

        return result;
    }
});
